package editor.project;


import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import javax.swing.tree.DefaultMutableTreeNode;

import org.apache.commons.io.FileUtils;
import org.jdom.Element;

import editor.Context;
import editor.project.ProjectFile.Type;


/**
 * Models a Java Project
 * 
 * @author Mark Dessain 
 *
 */
@SuppressWarnings("serial")
public class Project extends File
{
	// The different return codes when creating or loading a project
	public enum ReturnCode 
	{
		OK, 
		PROJECTEXISTS, 
		WRITEPERMISSIONS
	};
	
	private ProjectFile  mainClass = null;
	
	private int compileExitCode;
	private InputStream compileErrorStream;
	private InputStream compileInputStream;
	
	private ArrayList<ProjectFile> files = new ArrayList<ProjectFile>();
	private ArrayList<ProjectFile> projectFiles = new ArrayList<ProjectFile>();
	
	private DefaultMutableTreeNode top = null;
	private DefaultMutableTreeNode src = null;
	private DefaultMutableTreeNode defaultPackage = null;
	
	/**
	 * @param name of the project
	 * @param directory of the project
	 */
	public Project(String name, String directory)
	{
		super(directory + System.getProperty("file.separator") + name);		
	}

	/**
	 * @return returncode on if the project was sucessful
	 */
	public ReturnCode setup()
	{
		// Creates the initial directory structure
		boolean createProject = createProjectFolder(this);
		boolean createSource =  createProjectFolder(getDirectory("src"));
		boolean createTests = createProjectFolder(getDirectory("oracles"));
		boolean createBin = createProjectFolder(getDirectory("bin"));
		boolean createLib = createProjectFolder(getDirectory("lib"));

		// Checks it was set-up correctly
		if(createProject && createSource && createTests && createBin && createLib)
		{
			return ReturnCode.OK;
		}
		else
		{
			// Attempts to delete what has already been set-up
			delete();
			return ReturnCode.WRITEPERMISSIONS;
		}
	}

	/**
	 * @param displayFiles True if the files should be displayed
	 * @return the tree
	 */
	public DefaultMutableTreeNode load(Boolean displayFiles)
	{
		
		// Sets to top node and the children
		top = new DefaultMutableTreeNode(this);
		File[] children = listFiles();
		
		// Loop though the children
		for(File file: children)
		{			
			// Sets the current file and node
			ProjectFile rootFilesFolders = new ProjectFile(file.toString(), this, Type.FOLDER);
			DefaultMutableTreeNode node = new DefaultMutableTreeNode(rootFilesFolders);
			
			if(file.isDirectory() && file.getName().equals("src"))
			{
				this.src = node;
				rootFilesFolders.setType(Type.SRC);
				
				// Sets up the default package
				ProjectFile defaultP = new ProjectFile(file.toString(), this, Type.PACKAGE);
				//defaultP.setPackage(new Package(defaultP, null));
				defaultPackage = new DefaultMutableTreeNode(defaultP);
				node.add(defaultPackage);
				
				// Loads children
				loadPackages(rootFilesFolders, node, new Package(), displayFiles);
				top.add(node);
			}			
			else if(file.isDirectory() && (file.getName().equals("bin")))
			{
				// Ignore bin folder
			}
			else if(file.isDirectory())
			{
				//rootFilesFolders.setType(Type.FOLDER);
				loadNode(file, node);
				top.add(node);
			}
			else
			{
				rootFilesFolders.setType(Type.FILE);
				//top.add(node);
			}
			
		}
		
		return top;
	}

	
	/**
	 * @param packageFilesFolders the file
	 * @param top the top of the node
	 * @param parentPackage the parent of the file
	 * @param displayFiles should files be displayed
	 */
	private void loadPackages(ProjectFile packageFilesFolders, DefaultMutableTreeNode top, Package parentPackage, Boolean displayFiles)
	{
		File[] children = packageFilesFolders.listFiles();
		int fileCount = 0;

		for(File child: children)
		{	
			ProjectFile projectFile = new ProjectFile(child.toString(), this, Type.FILE);
			DefaultMutableTreeNode node = new DefaultMutableTreeNode(projectFile);
			
			System.out.println(child.getName());
			if(child.isHidden() || child.getName().substring(0,1).equalsIgnoreCase("."))
			{
				// Ignore hidden stuff
			}
			else if(child.isDirectory())
			{
				Package parent = new Package(projectFile, parentPackage);

				projectFile.setType(Type.PACKAGE);
				projectFile.setPackage(parent);
				//top.add(node);
				src.add(node);
				loadPackages(projectFile, node, parent, displayFiles);
				
			}
			else if(displayFiles)
			{
				fileCount++;
				
				projectFile.setPackage(parentPackage);
				
				if(child.getName().substring(child.getName().lastIndexOf(".") + 1).equalsIgnoreCase("java"))
				{
					projectFile.setType(Type.JAVA);
					projectFiles.add(projectFile);
					
				}
				else
				{
					projectFile.setType(Type.FILE);
				}

				files.add(projectFile);
				
				// If the file is directly in the source folder then add it to 
				// the default package instead
				if(top == src)
				{
					defaultPackage.add(node);
				}
				else
				{
					top.add(node);
				}
			}
		}
		
		// Means the package has no children, so its empty
		if(fileCount == 0 && packageFilesFolders.getType() != Type.SRC)
		{
			packageFilesFolders.setType(Type.PACKAGEEMPTY);
		}
	}
	
    /**
     * @param file file
     * @param top parent which the file should be added to
     */
    private void loadNode(File file, DefaultMutableTreeNode top) {

        // Recursively check for children files
        File[] children = file.listFiles();
        
        if (children != null) {
            for (File child : children) {
            	
            	ProjectFile projectFile;
            	
    			if(child.isHidden())
    			{
    				projectFile = null;
    			}
    			else
    			{
    			
            		if(child.listFiles() == null)
            		{

        				projectFile = new ProjectFile(child.getAbsolutePath(), this, Type.FILE);
        				
        					
        				files.add(projectFile);
            			
            		}
            		else
            		{
            			projectFile = new ProjectFile(child.getAbsolutePath(), this, Type.FOLDER);
            		}
                    
                    DefaultMutableTreeNode node = new DefaultMutableTreeNode(projectFile);
                    top.add(node);
                    loadNode(child, node);
    			}
            }
        }
    }  

    
    /**
     * @param file to be deleted
     * @param deleteChildren should the children also be deleted
     */
    public void deleteFile(ProjectFile file, boolean deleteChildren)
    {
    	if(deleteChildren)
    	{
			try {
				
				for(ProjectFile pFile: files)
				{
					if(pFile.getAbsolutePath().startsWith(file.getAbsolutePath()))
					{
						files.remove(pFile);
						projectFiles.remove(pFile);
					}
				}
				
				FileUtils.deleteDirectory(file);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    	}
    	else
    	{
    		
    		File[] files = file.listFiles();
    		if(files != null)
    		{
	    		for(File f: files)
	    		{
	    			if(!f.isDirectory())
	    			{
	    				f.delete();
	    			}
	    		}
    		}
    		
    		if(!file.isDirectory())
    		{
    			file.delete();
    			this.files.remove(file);
    			this.projectFiles.remove(file);
    		}
    	}
		
    	Context.getSideBarManager().getProjectManager().reload();
    }

	/**
	 * @param project to compare against
	 * @return True if the project already exists, False otherwise
	 */
	public boolean equals(Project project)
	{
		// If the project has the same name and directory then the project is equal
		return(this.getAbsolutePath().equalsIgnoreCase(project.getAbsolutePath()));
	}

	/**
	 * @param f to create
	 * @return True if it was created, False otherwise
	 */
	private boolean createProjectFolder(File f)
	{
		// If the folder already exists return true
		if(f.isDirectory())
		{
			return true;
		}

		// Otherwise attempt to create the folder
		if(f.mkdir())
		{
			return true;
		}

		// Otherwise a write error occurred
		return false; 		
	}
	
	/**
	 * @return incomplete
	 */
	public boolean execute()
	{
		System.out.println(mainClass + " " + mainClass.getClassFile() + " " + mainClass.getPackage().linkPackages() + " " + mainClass.getAbsolutePath());
		
		// TODO
		return true;
	}
	
	/**
	 * @return True if the project compiled, False otherwise
	 */
	public boolean compile()
	{
		Runtime runtime = Runtime.getRuntime();
		Process process = null;

		int timeout = 3000;	// 3 seconds

		StringBuilder fileList = new StringBuilder();
		
		for(ProjectFile file: projectFiles)
		{
			fileList.append(file.getAbsolutePath() + " ");
		}
		
		System.out.println("javac -classpath " + this + "/lib/* " + fileList.toString() + " -d " + this + "/bin/");
	    try {  			
			process = runtime.exec("javac -classpath " + this + "/lib/* " + fileList.toString() + " -d " + this + "/bin/");     
	    } catch (IOException e) {
			e.printStackTrace();
		}
	    
		compileErrorStream = process.getErrorStream();
		compileInputStream = process.getInputStream();

		while(timeout > 0)
		{
			try {
				compileExitCode = process.exitValue();
				System.out.println(compileExitCode);
				timeout = 0;
			} catch (IllegalThreadStateException e) {

				try {
					Thread.sleep(300);
					timeout =- 300;
				} catch (InterruptedException e1) {
					// Doesn't Matter
				}
			}
		}
		
		return true;
	}

	/**
	 * @return exit code
	 */
	public int getCompileExitCode() {
		return compileExitCode;
	}

	/**
	 * @return error stream
	 */
	public InputStream getCompileErrorStream() {
		return compileErrorStream;
	}

	/**
	 * @return input stream
	 */
	public InputStream getCompileInputStream() {
		return compileInputStream;
	}

	/**
	 * @return XML element
	 */
	public Element getXMLElement()
	{
		Element project = new Element("project");
		Element directory = new Element("directory");
		Element name = new Element("name");
		Element main = new Element("main");

		name.setText(this.getName());
		directory.setText(this.getParent());
		
		if(mainClass != null)
		{
			main.setText(mainClass.getAbsolutePath());
		}
		
		project.addContent(name);
		project.addContent(directory);
		project.addContent(main);

		return project;
	}

	/**
	 * @param directory to select
	 * @return file of directory within project
	 */
	public File getDirectory(String directory)
	{
		return new File(getAbsolutePath() + System.getProperty("file.separator") + directory + System.getProperty("file.separator"));
	}
	
	/**
	 * @param node to check against
	 * @param fileString to check for
	 * @return the file if it exists
	 */
	public ProjectFile findFile(DefaultMutableTreeNode node, String fileString)
	{        

    	ProjectFile file = (ProjectFile)node.getUserObject();
        	
    	if(file.getAbsolutePath().equals(fileString))
    	{
    		return file;
    	}
    	else
    	{
    		ProjectFile temp = null;
    		
            for(int i = 0; i < node.getChildCount(); i++)
            {
            	temp = findFile((DefaultMutableTreeNode)node.getChildAt(i), fileString);
            	
            	if(temp != null)
            	{
            		return temp;
            	}
            }
    	
            return temp;
    	}
    
	}
	
	/**
	 * @return get source node
	 */
	public DefaultMutableTreeNode getSrc()
	{
		return src;
	}
	
	/**
	 * @return get files node
	 */
	public ArrayList<ProjectFile> getFiles()
	{
		return files;
	}
	
	/**
	 * @return get project files
	 */
	public ArrayList<ProjectFile> getProjectFiles()
	{
		return projectFiles;
	}
	
	/**
	 * @param file to be the main class
	 */
	public void setMainClass(ProjectFile file)
	{
		mainClass = file;
	}
}
